package comms;

import javax.microedition.io.Connector;
import javax.microedition.io.DatagramConnection;
import javax.microedition.io.Datagram;

import ap.AgentPkt;
import agent.*;

import java.io.IOException;

/**
 * A class which represents a connection which uses UDP Datagrams in order
 * to either send or receieve mobile agents.
 * <p>
 * Runs in a seperate Thread if listening for connections otherwise waits for
 * send or close methods to be called.
 * 
 * @author Kevin
 * @see AgentConnection
 */
public class DatagramAgentConnection extends Thread implements AgentConnection {
	//The ConnectionManager object which created this object.
	private ConnectionManager owner;
	
	//The current DatagramConnection for this object.
	private DatagramConnection currentConnection;
	
	//The status of this connection as defined in the AgentConnection interface.
	private int connectionStatus;
	
	//The maximum length of a datagram as determined by the underlying UDP classes.
	private int maximumLength;
	
	//Whether this object is listening for connections of not.
	private boolean listening;

	/**
	 * Constructor for a connection which listens for Datagrams on the 
	 * supplied port.
	 * 
	 * @param port The port to listen on.
	 * @param o The ConnectionManager which created this object.
	 */
	public DatagramAgentConnection(ConnectionManager o, int port) {
		this.owner = o;
		this.connectionStatus = AgentConnection.RECEIVER_ONLY;
		
		try {
			this.currentConnection = (DatagramConnection)Connector.open("datagram://:" + port);
			this.maximumLength = this.currentConnection.getMaximumLength();
		} catch (IOException e) {
			e.printStackTrace();
		}
			
		this.listening = true;
		this.start();
	}
	
	/**
	 * Constructor for a connection which can be used to send agents via 
	 * UDP to the supplied address:port.
	 * 
	 * @param port The port to send over.
	 * @param address The address to send to.
	 * @param o The ConnectionManager which created this object.
	 */
	public DatagramAgentConnection(ConnectionManager o, int port, String address) {
		this.owner = o;
		this.connectionStatus = AgentConnection.SENDER_ONLY;
		this.listening = false;
		
		try {
			this.currentConnection = (DatagramConnection)Connector.open("datagram://" + address + ":" + port);
			this.maximumLength = this.currentConnection.getMaximumLength();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Constructor for a connection which can be used to send agents via UDP
	 * to the supplied address:port.
	 * <p>
	 * Can also be used to listen for incoming connections if the address is of
	 * length 0.
	 * 
	 * @param port The port to send over.
	 * @param address The address to send to.
	 * @param o The ConnectionManager which created this object.
	 */
	public DatagramAgentConnection(ConnectionManager o, String address, String port) {
		this.owner = o;
		
		try {
			this.currentConnection = (DatagramConnection)Connector.open("datagram://" + address + ":" + port);
			this.maximumLength = this.currentConnection.getMaximumLength();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if (address.length() > 0) {
			this.connectionStatus = AgentConnection.SENDER_ONLY;
			this.listening = false;
		} else {
			this.connectionStatus = AgentConnection.RECEIVER_ONLY;
			this.listening = true;
			this.start();
		}	

	}
	
	/**
	 * Run method for this Thread; constantly waits for incoming datagrams until
	 * such time as the closeConnection method for this object is called.
	 * 
	 */
	public void run() {
		Datagram datagram = null;
		try {
			datagram = this.currentConnection.newDatagram(this.maximumLength);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		while (this.listening && (datagram != null)) {
			try {
				this.currentConnection.receive(datagram);
				this.owner.agentReceived(datagram.getData(), datagram.getLength());
			} catch (IOException e) {
				e.printStackTrace();
			}			
		}
	}
	
	/**
	 * Sends an agent via the current connection.
	 * 
	 * @param agent The agent to be sent.
	 * @return True if send successful; false otherwise.
	 * @throws AgentConnectionException If connection could not be opened.
	 */	
	public boolean sendAgent(Agent agent) throws AgentConnectionException {
		if ((this.connectionStatus == AgentConnection.RECEIVER_ONLY) ||
			(this.connectionStatus == AgentConnection.CLOSED))
			throw new AgentConnectionException("Connection closed or not in send mode.");
		
		Datagram datagram = null;
		try {
			datagram = this.currentConnection.newDatagram(this.maximumLength);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if (datagram != null) {
			try {
				datagram.write(agent.toString().getBytes());
				this.currentConnection.send(datagram);
			} catch (IOException e) {
				e.printStackTrace();
			}
			return true;
		} else
			return false;
	}
	
	/**
	 * Sends an agent in the form of an AgentPkt object via the current connection.
	 * 
	 * @param agentPack The AgentPkt for the agent concerned.
	 * @return True if send successful; false otherwise.
	 * @throws AgentConnectionException If connection could not be opened.
	 */
	public boolean sendAgent(AgentPkt agentPack) throws AgentConnectionException {
		if ((this.connectionStatus == AgentConnection.RECEIVER_ONLY) ||
				(this.connectionStatus == AgentConnection.CLOSED))
				throw new AgentConnectionException("Connection closed or not in send mode.");
			
		Datagram datagram = null;
		try {
			datagram = this.currentConnection.newDatagram(this.maximumLength);
		} catch (IOException e) {
			e.printStackTrace();
		}
			
		if (datagram != null) {
			try {
				datagram.write(agentPack.getAgentString().getBytes());
				this.currentConnection.send(datagram);
			} catch (IOException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
			return true;
		} else
			return false;
	}
	
	/**
	 * Returns the saved status value of the connection as indicated by the static final
	 * integer values already defined.
	 * 
	 * @return The status of the connection as indicated by the static constants above.
	 */
	public int getStatus() {
		return this.connectionStatus;
	}
	
	/**
	 * Attempts to close the current connection as soon as possible.
	 *
	 */
	public synchronized void closeConnection() {
		this.listening = false;
		this.connectionStatus = AgentConnection.CLOSED;
		
		try {
			this.currentConnection.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
